use crate::access::Access;
use crate::attr::Attribute;
use crate::constant::Constant;
use crate::function::Function;
use crate::reader::Stream;
use crate::Config;

#[derive(Debug)]
pub struct Method {
	pub(crate) access: Access,
	pub(crate) name: String,
	pub(crate) rust_name: String,
	pub(crate) sign_java: String,
	pub(crate) sign: Function,
	#[allow(dead_code)]
	pub(crate) attrs: Vec<Attribute>,
}
impl Method {
	pub fn parse(s: &mut Stream, constants: &Vec<Constant>) -> Option<Self> {
		let access_flags = Access(s.read()?);
		let name_index: u16 = s.read()?;
		let name = constants.get(name_index as usize)?.to_string(constants)?;
		let sign_index: u16 = s.read()?;
		let sign_java = constants.get(sign_index as usize)?.to_string(constants)?;
		let sign = Function::from_str(&sign_java)?;
		let attr_count: u16 = s.read()?;
		let mut attrs = vec![];
		for _ in 0..attr_count {
			attrs.push(Attribute::parse(s, constants)?);
		}
		Some(Self {
			access: access_flags,
			rust_name: name.clone(),
			name,
			sign_java,
			sign,
			attrs,
		})
	}
	pub fn sign_u8(&self) -> String {
		format!("b\"{}\0\'", self.sign_java)
	}
	pub fn fmt_virtual<'a>(&'a self, pubtag: &'a str, class_fun: &'a str, cfg: &'a Config) -> FmtVirtual<'a> {
		FmtVirtual(self, pubtag, class_fun, cfg)
	}
	pub fn fmt_static<'a>(&'a self, pubtag: &'a str, class_fun: &'a str, cfg: &'a Config) -> FmtStatic<'a> {
		FmtStatic(self, pubtag, class_fun, cfg)
	}
}

pub struct FmtVirtual<'a>(pub &'a Method, pub &'a str, pub &'a str, pub &'a Config);
impl<'a> std::fmt::Display for FmtVirtual<'a> {
	fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
		if self.0.access.is_static() {
			return Ok(());
		}
		f.write_fmt(format_args!(
			r##"
{pubtag}fn {fun_name}<'a>(&'a self, env: &'a JEnv,{argn_obj}) -> Option<{rust_ret}> {{
	static CACHE: CachedID = CachedID::new();
	CACHE
		.get(|| Self::{class_fun}(Some(env))?.method(env, b"{name}\0", b"{sign_java}\0"))?
		.call(env, self.as_ref(), ({argn_pass}))
}}"##,
			name = self.0.name,
			fun_name = casbab::snake(&self.0.rust_name),
			sign_java = self.0.sign_java,
			rust_ret = self.0.sign.ret.fmt_jnim_type(true, self.3),
			argn_obj = self.0.sign.fmt_argn_obj(self.3),
			argn_pass = self.0.sign.fmt_argn_pass(),
			pubtag = self.1,
			class_fun = self.2,
		))
	}
}

pub struct FmtStatic<'a>(pub &'a Method, pub &'a str, pub &'a str, pub &'a Config);
impl<'a> std::fmt::Display for FmtStatic<'a> {
	fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
		if !self.0.access.is_static() {
			return Ok(());
		}
		f.write_fmt(format_args!(
			r##"
{pubtag}fn {fun_name}<'a>(env: &'a JEnv,{argn_obj}) -> Option<{rust_ret}> {{
	let class = Self::{class_fun}(Some(env))?;
	static CACHE: CachedID = CachedID::new();
	CACHE
		.get(|| class.method_static(env, b"{name}\0", b"{sign_java}\0"))?
		.call(env, class, ({argn_pass}))
}}"##,
			name = self.0.name,
			fun_name = casbab::snake(&self.0.rust_name),
			sign_java = self.0.sign_java,
			rust_ret = self.0.sign.ret.fmt_jnim_type(true, self.3),
			argn_obj = self.0.sign.fmt_argn_obj(self.3),
			argn_pass = self.0.sign.fmt_argn_pass(),
			pubtag = self.1,
			class_fun = self.2,
		))
	}
}
